#![allow(dead_code, unused_imports)]

mod utils;

use cfg_if::{cfg_if};
use std::collections::VecDeque;
use utils::{format_path};
use log::{Level, Log, Metadata, Record, SetLoggerError};

pub use utils::{Timezone, format_time};
pub use log::{LevelFilter};

cfg_if! {
    if #[cfg(target_arch = "wasm32")] {
        use web_sys::console;
        use wasm_bindgen::{JsValue};
    } else {
        use ansi_term::Color;
    }
}

#[cfg(not(target_arch = "wasm32"))]
static LOG_LEVEL_NAMES: [&str; 6] = ["N/A", "E", "W", "I", "D", "T"];

#[cfg(not(target_arch = "wasm32"))]
fn level_to_str(l: &Level) -> &'static str {
    LOG_LEVEL_NAMES[*l as usize]
}


pub struct Options {
    pub level: LevelFilter,
    pub print_level: bool,
    pub timezone: Timezone,
    pub colored: bool,
    pub mods: Vec<String>,
    pub notify: Option<Box<dyn Fn(Line) + Send + Sync + 'static>>,
}

impl Default for Options {
    fn default() -> Self {
        Self {
            level: LevelFilter::Trace,
            print_level: true,
            timezone: Timezone::Local,
            colored: true,
            mods: vec![],
            notify: None,
        }
    }
}

#[derive(Clone, Debug)]
pub struct Line {
    pub timestamp: String,
    pub level: LevelFilter,
    pub path_info: String,
    pub msg: String,
}

pub struct XLog {
    level: LevelFilter,
    print_level: bool,
    timezone: Timezone,
    colored: bool,
    mods: Vec<String>,
    notify: Option<Box<dyn Fn(Line) + Send + Sync + 'static>>,
}

impl XLog {
    pub fn new(opts: Options) -> XLog {
        let Options {
            level,
            print_level,
            timezone,
            colored,
            mods,
            notify
        } = opts;

        XLog {
            level,
            print_level,
            timezone,
            colored,
            mods,
            notify,
        }
    }

    pub fn set_notify(&mut self, notify: impl Fn(Line) + Send + Sync + 'static) {
        self.notify = Some(Box::new(notify));
    }

    pub fn init(self) -> Result<(), SetLoggerError> {
        log::set_max_level(log::STATIC_MAX_LEVEL);
        log::set_boxed_logger(Box::new(self))?;
        Ok(())
    }
}

impl Log for XLog {
    fn enabled(&self, metadata: &Metadata) -> bool {
        let target = metadata.target().to_string();
        let mods: Vec<&str> = target.split("::").collect();

        if self.mods.is_empty() == false {
            let finds: Vec<&String> = self.mods.iter().filter_map(|x| {
                if *x == mods[0].to_string() {
                    Some(x)
                } else {
                    None
                }
            }).collect();
            if finds.is_empty() {
                return false;
            }
        }

        metadata.level().to_level_filter() <= self.level
    }

    fn log(&self, record: &Record) {
        let pathname = format_path(
            record.file().unwrap_or("???"),
            record.line().unwrap_or(0),
        );

        if let Some(ref notify) = self.notify {
            let timestamp = format_time(&self.timezone);

            let msg = format!("{}", record.args());
            notify(Line {
                timestamp: timestamp.clone(),
                level: record.level().to_level_filter(),
                path_info: pathname.clone(),
                msg,
            })
        }

        // self.notify.as_ref().map(|f| {
        //     f(Line {
        //         timestamp: timestamp.clone(),
        //         level: record.level().to_level_filter(),
        //         path_info: pathname.clone(),
        //         msg,
        //     })
        // });

        if self.enabled(record.metadata()) {
            #[cfg(target_arch = "wasm32")]
            {
                let level = match self.print_level {
                    true => format!("{}", &record.level().as_str()),
                    false => "".to_string()
                };
                let msg = format!("{}", record.args());
                let level_pre = match record.level() {
                    Level::Trace => "color: white; background: #747474",
                    Level::Debug => "color: white; background: #2330da",
                    Level::Info => "color: white; background: #00831c",
                    Level::Warn => "color: white; background: #e17400",
                    Level::Error => "color: white; background: #b90008",
                };
                let console_log = match record.level() {
                    Level::Trace => console::debug_4,
                    Level::Debug => console::log_4,
                    Level::Info => console::info_4,
                    Level::Warn => console::warn_4,
                    Level::Error => console::error_4,
                };
                // let msg_pre = match record.level() {
                //     Level::Trace => "color: #inherit",
                //     Level::Debug => "color: #inherit",
                //     Level::Info => "color: #inherit",
                //     Level::Warn => "color: #e17400",
                //     Level::Error => "color: #b90008",
                // };
                let msg_pre = "color: inherit";

                console_log(
                    &JsValue::from(format!("%c{}%c {} %c{}",
                                           level, pathname, msg
                    )),
                    &JsValue::from(level_pre),
                    &JsValue::from("color: inherit"),
                    &JsValue::from(msg_pre),
                );
            }

            #[cfg(not(target_arch = "wasm32"))]
            {
                let timestamp = format_time(&self.timezone);
                let level = match self.print_level {
                    true => format!("[{}] ", level_to_str(&record.level())),
                    false => "".to_string()
                };

                let mut msg = format!("{}{}", level, record.args());

                let mut pathname = pathname;
                if self.colored {
                    // timestamp = Color::Fixed(250).paint(timestamp).to_string();
                    // let mut pathname =p pathname;
                    pathname = Color::Blue.paint(pathname.as_str()).to_string();

                    msg = match record.level() {
                        Level::Trace => Color::White.paint(msg).to_string(),
                        Level::Debug => Color::Blue.paint(msg).to_string(),
                        Level::Info => Color::Green.paint(msg).to_string(),
                        Level::Warn => Color::Yellow.paint(msg).to_string(),
                        Level::Error => Color::Red.paint(msg).to_string(),
                    };
                }

                let message = format!("{}{} {}", timestamp, pathname, msg);

                #[cfg(not(feature = "stderr"))]
                println!("{}", message);
            }

            #[cfg(feature = "stderr")]
            eprintln!("{}", message);
        }
    }

    fn flush(&self) {}
}

use std::sync::Once;

static INIT: Once = Once::new();

pub fn init_once(
    opts: Option<Options>,
) {
    INIT.call_once(|| {
        let opts = opts.unwrap_or_default();
        XLog::new(opts).init().unwrap();
    });
}

pub extern crate log;

// pub use log::{trace, debug, info, warn, error};

#[macro_export]
macro_rules! trace {
    // (target: $target:expr, $($arg:tt)+) => ({
    //     log::log!(target: $target, log::Level::Trace, $($arg)+)
    // });
    ($($arg:tt)*) => ({
        x_log::init_once(std::option::Option::None);
        $crate::log::log!(log::Level::Trace, $($arg)+);
    });
}

#[macro_export]
macro_rules! debug {
    ($($arg:tt)*) => ({
        x_log::init_once(std::option::Option::None);
        $crate::log::log!(log::Level::Debug, $($arg)+);
    });
}

#[macro_export]
macro_rules! info {
    ($($arg:tt)*) => ({
        x_log::init_once(std::option::Option::None);
        $crate::log::log!(log::Level::Info, $($arg)+);
    });
}

#[macro_export]
macro_rules! warn {
    ($($arg:tt)*) => ({
        x_log::init_once(std::option::Option::None);
        $crate::log::log!(log::Level::Warn, $($arg)+);
    });
}

#[macro_export]
macro_rules! error {
    ($($arg:tt)*) => ({
        x_log::init_once(std::option::Option::None);
        $crate::log::log!(log::Level::Error, $($arg)+);
    });
}
////////////////////////////////////////////////////////////////////////////////
#[doc(hidden)]
pub use utils::__print_val;

#[macro_export]
macro_rules! __val {
    ($fmt:expr, $($arg:tt)+) => (
        x_log::__print_val(file!(), line!(), format_args!($fmt, $($arg)+));
    )
}

/// value each line
#[macro_export]
macro_rules! val {
    ($v:expr) => {
        __val!("\n{}︎︎︎︎⏤►{:?}", stringify!($v), $v);
    };
    ($v:expr, $v2:expr) => {
        __val!("\n{}︎︎︎︎⏤►{:?} \n{}︎︎︎︎⏤►{:?}", stringify!($v), $v, stringify!($v2), $v2,);
    };
    ($v:expr, $v2:expr, $v3:expr) => {
        __val!("\n{}︎︎︎︎⏤►{:?} \n{}︎︎︎︎⏤►{:?} \n{}︎︎︎︎⏤►{:?}", stringify!($v), $v, stringify!($v2), $v2, stringify!($v3), $v3);
    };
    ($v:expr, $v2:expr, $v3:expr, $v4:expr) => {
        __val!("\n{}︎︎︎︎⏤►{:?} \n{}︎︎︎︎⏤►{:?} \n{}︎︎︎︎⏤►{:?} \n{}︎︎︎︎⏤►{:?}", stringify!($v), $v, stringify!($v2), $v2, stringify!($v3), $v3, stringify!($v4), $v4);
    };
    ($v:expr, $v2:expr, $v3:expr, $v4:expr, $v5:expr) => {
        __val!("\n{}︎︎︎︎⏤►{:?} \n{}︎︎︎︎⏤►{:?} \n{}︎︎︎︎⏤►{:?} \n{}︎︎︎︎⏤►{:?} \n{}︎︎︎︎⏤►{:?}", stringify!($v), $v, stringify!($v2), $v2, stringify!($v3), $v3, stringify!($v4), $v4, stringify!($v5), $v5);
    };
    ($v:expr, $v2:expr, $v3:expr, $v4:expr, $v5:expr, $v6:expr) => {
        __val!("\n{}︎︎︎︎⏤►{:?} \n{}︎︎︎︎⏤►{:?} \n{}︎︎︎︎⏤►{:?} \n{}︎︎︎︎⏤►{:?} \n{}︎︎︎︎⏤►{:?} \n{}︎︎︎︎⏤►{:?}", stringify!($v), $v, stringify!($v2), $v2, stringify!($v3), $v3, stringify!($v4), $v4, stringify!($v5), $v5, stringify!($v6), $v6);
    };
}

/// pretty format
#[macro_export]
macro_rules! valf {
    ($v:expr) => {
        __val!("\n{}︎︎︎︎⏤►{:#?}", stringify!($v), $v);
    };
    ($v:expr, $v2:expr) => {
        __val!("\n{}︎︎︎︎⏤►{:#?} \n{}︎︎︎︎⏤►{:#?}", stringify!($v), $v, stringify!($v2), $v2,);
    };
    ($v:expr, $v2:expr, $v3:expr) => {
        __val!("\n{}︎︎︎︎⏤►{:#?} \n{}︎︎︎︎⏤►{:#?} \n{}︎︎︎︎⏤►{:#?}", stringify!($v), $v, stringify!($v2), $v2, stringify!($v3), $v3);
    };
    ($v:expr, $v2:expr, $v3:expr, $v4:expr) => {
        __val!("\n{}︎︎︎︎⏤►{:#?} \n{}︎︎︎︎⏤►{:#?} \n{}︎︎︎︎⏤►{:#?} \n{}︎︎︎︎⏤►{:#?}", stringify!($v), $v, stringify!($v2), $v2, stringify!($v3), $v3, stringify!($v4), $v4);
    };
    ($v:expr, $v2:expr, $v3:expr, $v4:expr, $v5:expr) => {
        __val!("\n{}︎︎︎︎⏤►{:#?} \n{}︎︎︎︎⏤►{:#?} \n{}︎︎︎︎⏤►{:#?} \n{}︎︎︎︎⏤►{:#?} \n{}︎︎︎︎⏤►{:#?}", stringify!($v), $v, stringify!($v2), $v2, stringify!($v3), $v3, stringify!($v4), $v4, stringify!($v5), $v5);
    };
    ($v:expr, $v2:expr, $v3:expr, $v4:expr, $v5:expr, $v6:expr) => {
        __val!("\n{}︎︎︎︎⏤►{:#?} \n{}︎︎︎︎⏤►{:#?} \n{}︎︎︎︎⏤►{:#?} \n{}︎︎︎︎⏤►{:#?} \n{}︎︎︎︎⏤►{:#?} \n{}︎︎︎︎⏤►{:#?}", stringify!($v), $v, stringify!($v2), $v2, stringify!($v3), $v3, stringify!($v4), $v4, stringify!($v5), $v5, stringify!($v6), $v6);
    };
}
// macro_rules! valf {
//     ($v:expr) => {
//         __val!("\n⏤⏤ {}︎︎︎︎ ⏤⏤\n{:#?}", stringify!($v), $v);
//     };
//     ($v:expr, $v2:expr) => {
//         __val!("\n⏤⏤ {}︎︎︎︎ ⏤⏤\n{:#?} \n⏤⏤ {}︎︎︎︎ ⏤⏤\n{:#?}", stringify!($v), $v, stringify!($v2), $v2,);
//     };
//     ($v:expr, $v2:expr, $v3:expr) => {
//         __val!("\n⏤⏤ {}︎︎︎︎ ⏤⏤\n{:#?} \n⏤⏤ {}︎︎︎︎ ⏤⏤\n{:#?} \n⏤⏤ {}︎︎︎︎ ⏤⏤\n{:#?}", stringify!($v), $v, stringify!($v2), $v2, stringify!($v3), $v3);
//     };
//     ($v:expr, $v2:expr, $v3:expr, $v4:expr) => {
//         __val!("\n⏤⏤ {}︎︎︎︎ ⏤⏤\n{:#?} \n⏤⏤ {}︎︎︎︎ ⏤⏤\n{:#?} \n⏤⏤ {}︎︎︎︎ ⏤⏤\n{:#?} \n⏤⏤ {}︎︎︎︎ ⏤⏤\n{:#?}", stringify!($v), $v, stringify!($v2), $v2, stringify!($v3), $v3, stringify!($v4), $v4);
//     };
//     ($v:expr, $v2:expr, $v3:expr, $v4:expr, $v5:expr) => {
//         __val!("\n⏤⏤ {}︎︎︎︎ ⏤⏤\n{:#?} \n⏤⏤ {}︎︎︎︎ ⏤⏤\n{:#?} \n⏤⏤ {}︎︎︎︎ ⏤⏤\n{:#?} \n⏤⏤ {}︎︎︎︎ ⏤⏤\n{:#?} \n⏤⏤ {}︎︎︎︎ ⏤⏤\n{:#?}", stringify!($v), $v, stringify!($v2), $v2, stringify!($v3), $v3, stringify!($v4), $v4, stringify!($v5), $v5);
//     };
//     ($v:expr, $v2:expr, $v3:expr, $v4:expr, $v5:expr, $v6:expr) => {
//         __val!("\n⏤⏤ {}︎︎︎︎ ⏤⏤\n{:#?} \n⏤⏤ {}︎︎︎︎ ⏤⏤\n{:#?} \n⏤⏤ {}︎︎︎︎ ⏤⏤\n{:#?} \n⏤⏤ {}︎︎︎︎ ⏤⏤\n{:#?} \n⏤⏤ {}︎︎︎︎ ⏤⏤\n{:#?} \n⏤⏤ {}︎︎︎︎ ⏤⏤\n{:#?}", stringify!($v), $v, stringify!($v2), $v2, stringify!($v3), $v3, stringify!($v4), $v4, stringify!($v5), $v5, stringify!($v6), $v6);
//     };
// }

/// all in one line
#[macro_export]
macro_rules! valn {
    ($v:expr) => {
        __val!("{}︎︎︎︎⏤►{:?}", stringify!($v), $v);
    };
    ($v:expr, $v2:expr) => {
        __val!("{}︎︎︎︎⏤►{:?}, {}︎︎︎︎⏤►{:?}", stringify!($v), $v, stringify!($v2), $v2,);
    };
    ($v:expr, $v2:expr, $v3:expr) => {
        __val!("{}︎︎︎︎⏤►{:?}, {}︎︎︎︎⏤►{:?}, {}︎︎︎︎⏤►{:?}", stringify!($v), $v, stringify!($v2), $v2, stringify!($v3), $v3);
    };
    ($v:expr, $v2:expr, $v3:expr, $v4:expr) => {
        __val!("{}︎︎︎︎⏤►{:?}, {}︎︎︎︎⏤►{:?}, {}︎︎︎︎⏤►{:?}, {}︎︎︎︎⏤►{:?}", stringify!($v), $v, stringify!($v2), $v2, stringify!($v3), $v3, stringify!($v4), $v4);
    };
    ($v:expr, $v2:expr, $v3:expr, $v4:expr, $v5:expr) => {
        __val!("{}︎︎︎︎⏤►{:?}, {}︎︎︎︎⏤►{:?}, {}︎︎︎︎⏤►{:?}, {}︎︎︎︎⏤►{:?}, {}︎︎︎︎⏤►{:?}", stringify!($v), $v, stringify!($v2), $v2, stringify!($v3), $v3, stringify!($v4), $v4, stringify!($v5), $v5);
    };
    ($v:expr, $v2:expr, $v3:expr, $v4:expr, $v5:expr, $v6:expr) => {
        __val!("{}︎︎︎︎⏤►{:?}, {}︎︎︎︎⏤►{:?}, {}︎︎︎︎⏤►{:?}, {}︎︎︎︎⏤►{:?}, {}︎︎︎︎⏤►{:?}, {}︎︎︎︎⏤►{:?}", stringify!($v), $v, stringify!($v2), $v2, stringify!($v3), $v3, stringify!($v4), $v4, stringify!($v5), $v5, stringify!($v6), $v6);
    };
}
